package butines.core.game.graph;
import java.util.ArrayList;
import java.util.List;


public class Graph <NodeType extends Node<Edge<NodeType>>> {

	private int nextId;
	private ArrayList<NodeType> nodes = new ArrayList<NodeType>();
	
	public Graph() {
	}
	
	private void assertNodeChecks(int ... ids) {
		for (int id: ids) {
			if (id < 0 && id > getNodes().size() - 1) {
				throw new IllegalArgumentException("Invalid node ID: " + id);
			}
		}
	}
	
	public List<NodeType> getNodes() {
		return nodes;
	}

	public boolean isEmpty() {
		return getNodes().isEmpty();
	}
	
	public boolean isNodePresent(int id) {
		return (id >= 0 && id < getNodes().size()) && getNode(id) != null;
	}

	public boolean isEdgePresent(int idFrom, int idTo) {
		assertNodeChecks(idFrom, idTo);
		if (isNodePresent(idFrom) && isNodePresent(idTo)) {
			Edge<NodeType> edge = getEdge(idFrom, idTo);
			return edge != null;
		}
		return false;
	}

	public NodeType getNode(int id) {
		assertNodeChecks(id);
		return getNodes().get(id);
	}

	public NodeType addNode(NodeType node) {
		int id = nextId++;
		if (node != null) {
			node.setId(id);
		}
		getNodes().add(node);
		return node;
	}

	public void removeNode(NodeType node) {
		if (node == null) {
			throw new IllegalArgumentException("node is null");
		}
		for (Edge<NodeType> edge: node) {
			Edge<NodeType> reverse = edge.getTo().getEdge(node);
			if (reverse == null) {
				continue;
			}
			reverse.remove();
		}
		node.removeEdges();
		getNodes().set(node.getId(), null);
	}
	
	public Edge<NodeType> getEdge(int idFrom, int idTo) {
		assertNodeChecks(idFrom, idTo);
		if (isNodePresent(idFrom) && isNodePresent(idTo)) {
			NodeType from = getNode(idFrom);
			NodeType to = getNode(idTo);
			return from.getEdge(to);
		}
		return null;
	}

	public void addEdge(Edge<NodeType> edge) {
		if (edge.getFrom() == null) {
			throw new IllegalArgumentException("edge form is null");
		}
		if (edge.getTo() == null) {
			throw new IllegalArgumentException("edge to is null");
		}
		Node<Edge<NodeType>> from = edge.getFrom();
		Node<Edge<NodeType>> to = edge.getTo();
		if (!isEdgePresent(from.getId(), to.getId())) {
			from.addEdge(edge);
		}
	}
	
	public void removeEdge(NodeType from, NodeType to) {
		if (from == null) {
			throw new IllegalArgumentException("form is null");
		}
		if (to == null) {
			throw new IllegalArgumentException("to is null");
		}
		Edge<NodeType> edge = from.getEdge(to);
		if (edge != null) {
			edge.remove();
		}
	}

	public void removeEdges() {
		for (NodeType node: getNodes()) {
			if (node == null) {
				continue;
			}
			node.removeEdges();
		}
	}

	public int getNumNodes() {
		return getNodes().size();
	}

	public int getNumActiveNodes() {
		int count = 0;
		for (NodeType node: getNodes()) {
			if (node == null) {
				continue;
			}
			count++;
		}
		return count ;
	}

	public int getNumEdges() {
		int count = 0;
		for (NodeType node: getNodes()) {
			if (node == null) {
				continue;
			}
			count += node.getNumEdges();
		}
		return count;
	}

	public void clear() {
		removeEdges();
		getNodes().clear();
	}
	
}
